package template;

import java.util.ArrayList;
import logist.simulation.Vehicle;
import logist.plan.Action;
import logist.plan.Action.Delivery;
import logist.plan.Action.Move;
import logist.plan.Action.Pickup;
import logist.task.Task;
import logist.task.TaskSet;
import logist.topology.Topology;
import logist.topology.Topology.City;

public class State implements Comparable<State>{

	private Topology topology;
	private Vehicle vehicle;
	private ArrayList<Action> listActions;
	private City currentCity;
	private TaskSet carriedTasks;
	private ArrayList<Task> unfinishedTask;
	private double pastCost;
	private double futureCost;
	private int[] taskPositions;
	private int level;
	
	public static ArrayList<State> stateList = new ArrayList<State>();

	// Constructor of the class State,		
	// for cases where the hashmap is not initialized
	// we will only use this constructor for the initialization of the initial State
	public State(Topology topology, Vehicle vehicle, TaskSet tasks) {

		this.topology = topology;
		this.vehicle = vehicle;
		this.level = 0;
		this.listActions = new ArrayList<Action>();
		this.unfinishedTask = new ArrayList<Task>();

		this.currentCity = vehicle.getCurrentCity();
		this.carriedTasks = TaskSet.copyOf(vehicle.getCurrentTasks());

		int maxTaskNumber = -1;
		for (Task task: tasks) {
			unfinishedTask.add(task);
			if (task.id > maxTaskNumber) {
				maxTaskNumber = task.id;
			}
		}
		

		for (Task task: carriedTasks) {
			unfinishedTask.add(task);
			//taskLocation.put(task, currentCity);
			if (task.id > maxTaskNumber) {
				maxTaskNumber = task.id;
			}
		}

		
		
		this.taskPositions = new int[maxTaskNumber+1];
		for (int i = 0; i < taskPositions.length; i++) {
			taskPositions[i] = -1;
		}
		
		for (Task task: tasks) {
			taskPositions[task.id] = task.pickupCity.id;
		}

		for (Task task: carriedTasks) {
			taskPositions[task.id] = currentCity.id;
		}
		

		this.pastCost = 0;

//		this.futureCost = 0;
//		for (Task task: unfinishedTask) {
//			futureCost += getTaskLocation(task.id).distanceTo(task.deliveryCity) * task.weight;
//		}
//		futureCost = futureCost / (double)vehicle.capacity();

		
		this.futureCost = -1;
		for (Task task: unfinishedTask) {
			if (futureCost == -1) {
				futureCost = getTaskLocation(task.id).distanceTo(task.deliveryCity);
				futureCost += getTaskLocation(task.id).distanceTo(currentCity);
			} else {
				double aux = getTaskLocation(task.id).distanceTo(task.deliveryCity);
				aux += getTaskLocation(task.id).distanceTo(currentCity);
				if (aux > futureCost){
					futureCost = aux;
				}
			}
		}
	
	}
	
	public City getTaskLocation(int taskNum){

		for (City city: topology.cities()){
			if (city.id == this.taskPositions[taskNum])
				return city;
		}
		
		return currentCity;
	}
	
	

	public State(Topology topology
			, Vehicle vehicle
			, City newCurrentCity
			, TaskSet newCarriedTasks
			, ArrayList<Task> newUnfinishedTask
			, ArrayList<Action> newListActions
			, double newPastCost
			, int[] newTaskPositions 
			, int newLevel) {

		this.topology = topology;
		this.vehicle = vehicle;
		this.listActions = new ArrayList<Action>(newListActions);;
		this.unfinishedTask = new ArrayList<Task>(newUnfinishedTask);
		this.currentCity = newCurrentCity;
		this.carriedTasks = TaskSet.copyOf(newCarriedTasks);
		this.taskPositions = newTaskPositions.clone();
		this.level = newLevel;
		
		//check if delivery is necessary
		for (Task carriedTask: carriedTasks){
			if (carriedTask.deliveryCity.id == currentCity.id){
				carriedTasks.remove(carriedTask);
				unfinishedTask.remove(carriedTask);
				listActions.add(new Delivery(carriedTask));
			}
		}

		this.pastCost = newPastCost;
		this.futureCost = -1;

		for (Task task: unfinishedTask) {
			if (futureCost == -1) {
				futureCost = getTaskLocation(task.id).distanceTo(task.deliveryCity);
				futureCost += getTaskLocation(task.id).distanceTo(currentCity);
			} else {
				double aux = getTaskLocation(task.id).distanceTo(task.deliveryCity);
				aux += getTaskLocation(task.id).distanceTo(currentCity);
				if (aux > futureCost){
					futureCost = aux;
				}
			}
		}

//		this.futureCost = 0;
//		for (Task task: unfinishedTask) {
//			futureCost += this.getTaskLocation(task.id).distanceTo(task.deliveryCity) * task.weight;
//		}
//		futureCost = futureCost / (double)vehicle.capacity();
	}

	public ArrayList<Task> getUnfinishedTask() {
		return this.unfinishedTask;
	}

	public ArrayList<Action> getListActions() {
		return this.listActions;
	}

	public boolean isFinalState() {
		return this.unfinishedTask.size() == 0;
	}

	public City getCurrentCity() {
		return currentCity;
	}


	//returns all possible states after pickup
	public ArrayList<TaskSet> possiblePickUpTaskSets(ArrayList<Task> listAvailableTasks, int maxWeight) {
		ArrayList<TaskSet> possiblePickUpTaskSets = new ArrayList<TaskSet>();
		if (listAvailableTasks.isEmpty() || maxWeight <= 0) {
			return possiblePickUpTaskSets;
		} else {
			Task firstTask = listAvailableTasks.get(0);
			listAvailableTasks.remove(0);

			ArrayList<TaskSet> aux = possiblePickUpTaskSets(listAvailableTasks, maxWeight); 
			possiblePickUpTaskSets.addAll(aux);

			aux = possiblePickUpTaskSets(listAvailableTasks, maxWeight-firstTask.weight); 

			if (aux.isEmpty()) {
				TaskSet newTaskSet = TaskSet.noneOf(carriedTasks);
				newTaskSet.add(firstTask);
				possiblePickUpTaskSets.add(newTaskSet);
			} else {
				for (TaskSet taskSet: aux) {
					TaskSet newTaskSet = TaskSet.copyOf(taskSet);
					newTaskSet.add(firstTask);
					possiblePickUpTaskSets.add(0, newTaskSet);
				}
			}
		}
		return possiblePickUpTaskSets;
	}


	private ArrayList<Task> getAvailableTasks() {

		ArrayList<Task> availableTasks = new ArrayList<Task>();

		for (Task task: unfinishedTask){
			if (task.pickupCity.id == currentCity.id && !carriedTasks.contains(task)){
				if (carriedTasks.weightSum()+task.weight <= vehicle.capacity())
					availableTasks.add(task);
			}
		}


		return availableTasks;
	}

	private ArrayList<State> succ() {

		ArrayList<State> listNextStates = new ArrayList<State>();

		ArrayList<TaskSet> possiblePickUpTaskSets = possiblePickUpTaskSets(getAvailableTasks(), vehicle.capacity()-carriedTasks.weightSum());

		possiblePickUpTaskSets.add(TaskSet.noneOf(carriedTasks));

		for (City neighborCity: currentCity.neighbors()) {
			for (TaskSet tasks : possiblePickUpTaskSets) {
				ArrayList<Action> newListActions = new ArrayList<Action>(listActions);
				TaskSet newCarriedTasks = TaskSet.copyOf(carriedTasks);
				for (Task task: tasks){
					newListActions.add(new Pickup(task));
					newCarriedTasks.add(task);
				}
				newListActions.add(new Move(neighborCity));
				int [] newTaskPositions =  taskPositions.clone();
				
				for (Task task: newCarriedTasks) {
					newTaskPositions[task.id] = neighborCity.id;
				}
				double newPastCost = pastCost + currentCity.distanceTo(neighborCity);
				State newState = new State(topology, vehicle, neighborCity, newCarriedTasks, unfinishedTask, newListActions, newPastCost, newTaskPositions, this.level+1);
				listNextStates.add(newState);
			}

		}

		return listNextStates;
	}
	
	public ArrayList<State> bfsSucc() {
		ArrayList<State> listNextStates = succ();
		ArrayList<State> succStates = new ArrayList<State>();
		for (State nextState: listNextStates) {
			if ( nextState.bfsIsToBeAdded() ) {
				succStates.add(nextState);
				stateList.add(nextState);
			}
		}
		return succStates;
	}

	public ArrayList<State> aStarSucc(ArrayList<State> Q) {
		ArrayList<State> listNextStates = succ();
		ArrayList<State> succStates = new ArrayList<State>();
		for (State nextState: listNextStates) {
			//State oldStete = nextState.aStarIsToBeAdded();
			if ( nextState.aStarIsToBeAdded()) {
				
				if ( nextState.bfsIsToBeAdded()){
					stateList.add(nextState);
					succStates.add(nextState);
				} else {
					State oldState = null;
					for (State state : stateList){
						if ( nextState.equals(state) ){
							oldState = state;
						}
					}
					
					int oldStateIndex = stateList.indexOf(oldState);
					stateList.set(oldStateIndex, nextState);
					
					oldStateIndex = Q.indexOf(oldState);
					if (oldStateIndex > -1) {
						Q.set(oldStateIndex, nextState);
					} else {
						succStates.add(nextState);
					}
				}
				
			}
		}
		return succStates;
	}

	
	public int compareTo(State s) {
		if (this.pastCost+this.futureCost < s.pastCost+s.futureCost)
			return -1;
		else {
			if (this.pastCost+this.futureCost == s.pastCost+s.futureCost && this.pastCost > s.pastCost) {
				return -1;
			} else if (this.pastCost+this.futureCost == s.pastCost+s.futureCost && this.pastCost == s.pastCost) {
				return 0;
			}
		}
		
		return 1;
	}

	public boolean equals(State s) {
		if (this.currentCity.id != s.currentCity.id)
			return false;
		for (int i = 0; i < taskPositions.length; i ++ ) {
			if (taskPositions[i] != s.taskPositions[i]) {
				return false;
			}
		} 
				
		return true;
	}

	public boolean bfsIsToBeAdded() {
		for (State state : stateList){
			if ( this.equals(state) ){
				return false;
			}
		}
		return true;
	}
		
	public boolean aStarIsToBeAdded() {
		for (State state : stateList){
			if ( this.equals(state)  && this.pastCost+this.futureCost  >= state.pastCost+state.futureCost ){
				return false;
			}
		}
		return true;
	}
	
	public double getPastCost() {
		return this.pastCost;
	}
	
	public double getFutureCost() {
		return this.futureCost;
	}
	
	
	public void printStatus() {
//		System.out.print("cCity : " + String.format("%3s %10s", currentCity.id, currentCity.name) + " size " + taskLocation.size());
//		for (Map.Entry<Task, City> entry: this.taskLocation.entrySet()) {
//			System.out.print(" TId : " + entry.getKey().id + " CId " + String.format("%3s", entry.getValue().id));
//		}
//		System.out.println();

		System.out.print("cCity : " + String.format("%3s %10s", currentCity.id, currentCity.name) + " level " + String.format("%2s", this.level));
		for (int i = 0; i < taskPositions.length; i++) {
			System.out.print(" TId : " + i + " CId " + String.format("%3s", taskPositions[i]));
		}
		System.out.print("  cost " + String.format("%6s %6s %6s", this.futureCost+this.pastCost,this.pastCost, this.futureCost) );
		System.out.println();
		
	}
	
	public void debug() {
		System.out.println(currentCity.name + ": " + unfinishedTask.size() + " " + pastCost + " " + futureCost + " " + (pastCost+futureCost));
	}

}